﻿using System;
using System.Collections.Generic;
using System.Text;
using PostSharp.Laos;
using B4ALL.Cache.Singleton.Contract;
using System.Reflection;
using PostSharp.Extensibility;
using B4ALL.Common;
using B4ALL.JITL;

namespace B4ALL.Cache.Singleton
{
    [Serializable]
    [MulticastAttributeUsage(MulticastTargets.Class | MulticastTargets.Struct)]
    public class CacheSingletonAttribute : JITLObjectAttribute
    {

        /// <summary>
        /// Attribute for addind ICacheSingleton interface, CacheSingleton inheritance, and aspects on all GET methods
        /// </summary>
        /// <param name="element"></param>
        /// <param name="collection"></param>
        public override void ProvideAspects(object element, LaosReflectionAspectCollection collection)
        {
            // Get the target type.
            Type targetType = (Type)element;

            // On the type, add a Composition aspect to implement the INotifyPropertyChanged interface.
            if (targetType.UnderlyingSystemType.BaseType.GetCustomAttributes(typeof(CacheSingletonAttribute), true).Length == 0)
            {
                collection.AddAspect(targetType, new AddCacheSingletonInterfaceAspect());
            }

            // Add a OnMethodBoundaryAspect on each writable non-static property.
            foreach (PropertyInfo property in targetType.UnderlyingSystemType.GetProperties())
            {
                if (property.DeclaringType == targetType)
                {
                    if (property.CanRead)
                    {
                        MethodInfo method = property.GetGetMethod();

                        if (!method.IsStatic)
                        {
                            collection.AddAspect(method, new OnCacheSingletonGetAspect());
                        }
                    }

                    if (property.CanWrite)
                    {
                        MethodInfo method = property.GetSetMethod();

                        if (!method.IsStatic)
                        {
                            collection.AddAspect(method, new OnCacheSingletonGetAspect());
                        }
                    }

                }
            }
        }

        /// <summary>
        /// Object that implements this aspect must at least have 1 property with CacheSingletonIdentifier
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public override bool CompileTimeValidate(object element)
        {
            Int32 countIdentifiers = 0;
            Type targetType = (Type)element;

            CacheSingletonService cSService = new CacheSingletonService();

            foreach (PropertyInfo property in cSService.GetIdentificationProperties(targetType.UnderlyingSystemType))
            {
                Object[] propIdentifiers = property.GetCustomAttributes(typeof(CacheSingletonIdentifier), true);
                countIdentifiers += propIdentifiers.Length;
            }

            if (countIdentifiers == 0)
            {
                throw new Exception("CacheSingleton class must declare at least one CacheSingletonIdentifier attribute");
                return false;
            }
            else
                return true;

        }
    }

    [Serializable]
    internal class OnCacheSingletonGetAspect : OnMethodBoundaryAspect
    {
        public override void OnEntry(MethodExecutionEventArgs eventArgs)
        {
            JITLContext ctx = Context.GetContext<JITLContext>();

            if (ctx == null || ctx.DelayLoad)
            {
                ICacheSingleton cacheSingleton = (ICacheSingleton)eventArgs.Instance;

                if (cacheSingleton.IsDummyObject && !cacheSingleton.IsLoaded)
                {
                    cacheSingleton.Load(eventArgs.Instance);
                }
            }
        }
    }

    [Serializable]
    internal class AddCacheSingletonInterfaceAspect : CompositionAspect
    {
        public override object CreateImplementationObject(PostSharp.Laos.InstanceBoundLaosEventArgs eventArgs)
        {
            return new CacheSingleton();
        }

        public override Type GetPublicInterface(Type containerType)
        {
            return typeof(ICacheSingleton);
        }
    }

}
